/*
 * Copyright (c) 2011, Nikolaus Moll
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 * * Redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution.
 * * Neither the name of the gradecalculator.net nor the
 * names of its contributors may be used to endorse or promote products
 * derived from this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
 * DAMAGE.
 */
package net.gradecalculator.core.adapted;

import net.gradecalculator.core.GradeFactoryUtil;
import net.gradecalculator.core.interfaces.ICourse;
import net.gradecalculator.core.interfaces.IElectiveCourse;
import net.gradecalculator.core.interfaces.IModule;
import net.gradecalculator.core.interfaces.IStudent;
import net.gradecalculator.core.interfaces.results.IResult;
import net.gradecalculator.core.interfaces.results.IResultType;
import net.gradecalculator.plugin.grade.interfaces.ICourseGrade;
import net.gradecalculator.plugin.grade.interfaces.IGradeFactory;
import net.gradecalculator.plugin.grade.interfaces.IModuleGrade;

public class AdaptedModule {

	private static final class Result {
		private IModuleGrade grade;
		private boolean isGraded;
		private float ects;

		private Result() {}
	}

	private final IModule module;
	private final IStudent student;

	private final Result[] results;

	private final IGradeFactory fab;

	private int lastHash = 0;

	public AdaptedModule(final IStudent student, final IModule module) {
		this.module = module;
		this.student = student;
		fab = GradeFactoryUtil.getInstance().getFab(student.getUniversity().getCountryCode());

		results = new Result[student.getResultTypes().size()];
		for (int i = 0; i < results.length; i++) {
			results[i] = new Result();
		}
	}

	public IModule getModule() {
		return module;
	}

	private boolean getPassed(final ICourse c, final IResult r, final IResultType type) {
		if (c.getUngradedPerformance().isEmpty() && c.getGradedPerformance().isEmpty()) {
			if (c instanceof IElectiveCourse) {
				return false;
			}
		}

		if (r == null) {
			return false;
		}

		if (!c.getUngradedPerformance().isEmpty()) {
			if (!r.getPracticalResult(type)) {
				return false;
			}
		}

		if (!c.getGradedPerformance().isEmpty()) {
			if (!fab.getValidator().isValid(r.getGrade(type))) {
				return false;
			}
		}

		return true;
	}

	private void validate() {
		final int currentHash = hashCode();
		if (currentHash == lastHash) {
			return;
		}
		lastHash = currentHash;

		for (int i = 0; i < results.length; i++) {
			results[i].ects = 0;
			results[i].isGraded = false;
			results[i].grade = fab.createModuleGrade();
		}

		for (final ICourse course : module.getCourses()) {
			// TODO

			final IResult result = student.getResult(course);
			final ICourse usedCourse;
			if ((course instanceof IElectiveCourse) && (result.getCourse() != null)) {
				usedCourse = result.getCourse(); // TODO fix this :-)
			} else {
				usedCourse = course;
			}

			for (final IResultType type : student.getResultTypes()) {
				if (!getPassed(usedCourse, result, type)) { // or course...
					continue;
				}

				results[type.ordinal()].ects += course.getECTS();

				if (!usedCourse.getGradedPerformance().isEmpty()) {
					final ICourseGrade courseGrade = fab.createCourseGrade(result.getGrade(type), course.getECTS());
					results[type.ordinal()].grade.add(courseGrade);
					results[type.ordinal()].isGraded = true;
				}
			}
		}
	}

	public IModuleGrade getGrade(final IResultType passed) {
		validate();
		return results[passed.ordinal()].grade;
	}

	public float getECTS(final IResultType passed) {
		validate();
		return Math.min(results[passed.ordinal()].ects, module.getECTS());
	}

	public boolean isGraded(final IResultType passed) {
		validate();
		return results[passed.ordinal()].isGraded;
	}

	@Override
	public int hashCode() {
		int result = 17;
		for (final ICourse c : module.getCourses()) {
			final IResult r = student.getResult(c);
			result = 31 * result + ((r == null) ? 0 : r.hashCode());
			result = 31 * result + Float.floatToIntBits(c.getECTS());
		}
		return result;
	}
}
